﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SharpDepend.Storage.DataFormats
{
    public class AlexBinary : IDataFormat
    {
        /*
         * short        - string count
         */

        /*
         * byte         - length of storage string
         * byte[] vari  - storage string
         */

        /*
         * short        - string node name index
         * 
         * short        - Propertiy count
         * short        - Property string index
         *| byte        - Property string value length
         *| byte[] vari - Property string value
         * 
         * short        - SubNode count
         */

        private List<string> stringStorage = new List<string>();
        private int position = 0;

        //---------------------------------------------------------------------------------------
        int getString(string _string)
        {
            for (int i = 0; i < stringStorage.Count; i++)
            {
                if (stringStorage[i] == _string)
                {
                    // Hitta sin string
                    return i;
                }
            }

            // Annars så lägger vi in strängen
            stringStorage.Add(_string);
            return stringStorage.Count - 1;
        }

        //---------------------------------------------------------------------------------------
        string getStringFromIndex(int fromIndex)
        {
            return stringStorage[fromIndex];
        }

        //---------------------------------------------------------------------------------------
        void catchString(CustomNode node)
        {
            // Sparar nodens namn
            getString(node.Name);

            // Letar igenom alla properties och sparar deras namn
            for (int i = 0; i < node.Properties.Count; i++)
            {
                getString(node.Properties[i].Name);
            }

            // Sen letar vi igenom rensten av alla subnoder
            for (int i = 0; i < node.Nodes.Count; i++)
            {
                catchString(node.Nodes[i]);
            }
        }

        //---------------------------------------------------------------------------------------
        public override void Save(CustomNode headNodeToSaveFrom)
        {
            // Leta reda på alla Namn-strängar och spara dom i en lista
            writeBinaryHeader(headNodeToSaveFrom);

            writeBinaryScope(headNodeToSaveFrom);
        }

        //---------------------------------------------------------------------------------------
        void writeBinaryHeader(CustomNode node)
        {
            catchString(node);

            int headerLength = 0;
            headerLength += sizeof(short);
            for (int i = 0; i < stringStorage.Count; i++)
            {
                headerLength += sizeof(byte);
                headerLength += sizeof(byte) * getStringFromIndex(i).Length;
            }

            byte[] headerArray = new byte[headerLength];

            putShort(headerArray, (short)stringStorage.Count);
            for (int i = 0; i < stringStorage.Count; i++)
            {
                byte[] str = Encoding.UTF8.GetBytes( getStringFromIndex(i) );
                put(headerArray, (byte)str.Length);
                put(headerArray, str);
            }

            Write(headerArray);
        }

        //---------------------------------------------------------------------------------------
        void writeBinaryScope(CustomNode node)
        {
            // header
            int scopeLength = 0;
            short nodeNameIndex = (short)getString(node.Name);

            // properties
            short propertyCount = (short)node.Properties.Count;
            short[] propertyNameIndex = new short[propertyCount];
            byte[] propertyValuesLength = new byte[propertyCount];
            byte[][] propertyValues = new byte[propertyCount][];
            for (int i = 0; i < propertyCount; i++)
            {
                propertyNameIndex[i] = (short)getString(node.Properties[i].Name);

                if (node.Properties[i].Value == null)
                {
                    propertyValues[i] = new byte[0];
                    propertyValuesLength[i] = (byte)255;
                }
                else
                {
                    propertyValues[i] = Encoding.UTF8.GetBytes( node.Properties[i].Value );
                    propertyValuesLength[i] = (byte)propertyValues[i].Length;
                }
                
            }

            // subnodes
            short subnodeCount = (short)node.Nodes.Count;

            // Calculate header-length
            scopeLength += sizeof(short); // Node Name index

            // Properties length
            scopeLength += sizeof(short); // Property count
            scopeLength += sizeof(short) * propertyNameIndex.Length; // Property name indexs
            scopeLength += sizeof(byte) * propertyValuesLength.Length; // Property value lengths
            for (int i = 0; i < propertyValues.Length; i++) scopeLength += sizeof(byte) * propertyValues[i].Length;

            scopeLength += sizeof(short);

            // Write binary scope
            byte[] scope = new byte[scopeLength];
            position = 0;
            putShort(scope, nodeNameIndex);

            // Properties length
            putShort(scope, propertyCount); // Property count
            for (int i = 0; i < propertyCount; i++)
            {
                putShort(scope, propertyNameIndex[i]); // Property name indexs
                put(scope, propertyValuesLength[i]);
                put(scope, propertyValues[i]);
            }

            putShort(scope, subnodeCount);

            // Save binary scope to file
            Write(scope);

            // Save subnodes
            for (int i = 0; i < node.Nodes.Count; i++)
            {
                writeBinaryScope(node.Nodes[i]);
            }
        }

        //---------------------------------------------------------------------------------------
        public override void Load(CustomNode headNodeToLoadTo)
        {
            readBinaryHeader();

            readBinaryScope(headNodeToLoadTo);
        }

        //---------------------------------------------------------------------------------------
        void readBinaryHeader()
        {
            // Hämtar antalet strängar
            short stringCount = ReadShort();

            for (int i = 0; i < stringCount; i++)
            {
                // Hämtar längden på strängen
                short stringLength = ReadByte();

                // Hämtar strängen
                byte[] stringArray = new byte[stringLength];
                Read(stringArray, 0, stringArray.Length);
                getString(Encoding.UTF8.GetString(stringArray, 0, stringArray.Length));
            }
        }

        //---------------------------------------------------------------------------------------
        void readBinaryScope(CustomNode toNode)
        {
            // Header
            short nodeNameIndex = ReadShort();
            string name = getStringFromIndex(nodeNameIndex);

            toNode.Name = name;

            // Properties
            short propertyCount = ReadShort();
            for (int i = 0; i < propertyCount; i++)
            {
                short propertyNameIndex = ReadShort();
                byte propValueLength = ReadByte();

                if (propValueLength == 255) // Reserved for NULL
                {
                    string propValue = null;
                    Property property = new Property(getStringFromIndex(propertyNameIndex), propValue);
                    toNode.AddProperty(property);
                }
                else
                {
                    byte[] propValueBytes = new byte[propValueLength];

                    Read(propValueBytes, 0, propValueBytes.Length);

                    string propValue = Encoding.UTF8.GetString(propValueBytes, 0, propValueBytes.Length); // VERKAR BLI FELLL HÄRA; TITTA SEN!!!!!!!!!!!

                    Property property = new Property(getStringFromIndex(propertyNameIndex), propValue);
                    toNode.AddProperty(property);
                }
            }

            // Subnodes
            short subnodeCount = ReadShort();

            // Hämtar dom andra binary-scoperna
            for (int i = 0; i < subnodeCount; i++)
            {
                // Ladda nästa node-scope
                CustomNode subnode = new CustomNode();
                toNode.AddNode(subnode);
                readBinaryScope(subnode);
            }
        }

        //---------------------------------------------------------------------------------------
        void put(byte[] data, int value)
        {
            unchecked
            {
                data[position++] = (byte)(value >> 24);
                data[position++] = (byte)(value >> 16);
                data[position++] = (byte)(value >> 8);
                data[position++] = (byte)value;
            }
        }

        //---------------------------------------------------------------------------------------
        void putShort(byte[] data, short value)
        {
            unchecked
            {
                data[position++] = (byte)(value >> 8);
                data[position++] = (byte)value;
            }
        }

        //---------------------------------------------------------------------------------------
        void put(byte[] data, byte value)
        {
            data[position++] = value;
        }

        //---------------------------------------------------------------------------------------
        void put(byte[] data, int[] value)
        {
            for (int i = 0; i < value.Length; i++)
            {
                unchecked
                {
                    data[position++] = (byte)(value[i] >> 24);
                    data[position++] = (byte)(value[i] >> 16);
                    data[position++] = (byte)(value[i] >> 8);
                    data[position++] = (byte)value[i];
                }
            }
        }

        //---------------------------------------------------------------------------------------
        void put(byte[] data, byte[] value)
        {
            for (int i = 0; i < value.Length; i++)
            {
                data[position++] =  value[i];
            }
        }
    }
}
